package cn.iocoder.yudao.module.trade.order.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.*;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.business.basic.dao.repository.SerialNoRedisRepository;
import cn.iocoder.yudao.framework.business.evaluation.enums.ScoreLevelEnum;
import cn.iocoder.yudao.framework.business.order.pojo.vo.OrderSubmitVO;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.Assertions;
import cn.iocoder.yudao.framework.common.exception.KnownServiceException;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.util.TransactionUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.api.dine.enums.DineStatusEnum;
import cn.iocoder.yudao.module.api.infrastructure.config.ConfigApiV2;
import cn.iocoder.yudao.module.api.infrastructure.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.api.infrastructure.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.api.infrastructure.notify.dto.NotifySendSingleToUserReqDTO;
import cn.iocoder.yudao.module.api.infrastructure.social.SocialClientApi;
import cn.iocoder.yudao.module.api.infrastructure.social.dto.SocialWxaSubscribeMessageSendReqDTO;
import cn.iocoder.yudao.module.api.member.address.MemberAddressApi;
import cn.iocoder.yudao.module.api.member.address.dto.MemberAddressRespDTO;
import cn.iocoder.yudao.module.api.member.level.MemberLevelApi;
import cn.iocoder.yudao.module.api.pay.bill.enums.BillTypeEnum;
import cn.iocoder.yudao.module.api.pay.order.PayOrderApi;
import cn.iocoder.yudao.module.api.pay.order.dto.PayOrderCreateDTO;
import cn.iocoder.yudao.module.api.pay.order.eums.UnifiedOrderStatusEnum;
import cn.iocoder.yudao.module.api.pay.order.vo.PayOrderVO;
import cn.iocoder.yudao.module.api.pay.refund.PayRefundApi;
import cn.iocoder.yudao.module.api.pay.refund.dto.PayRefundCreateDTO;
import cn.iocoder.yudao.module.api.pay.refund.enums.UnifiedRefundStatusEnum;
import cn.iocoder.yudao.module.api.pay.refund.vo.PayRefundVO;
import cn.iocoder.yudao.module.api.product.evaluation.ProductEvaluationApi;
import cn.iocoder.yudao.module.api.product.evaluation.dto.ProductEvaluationCreateReqDTO;
import cn.iocoder.yudao.module.api.product.sku.ProductSkuApi;
import cn.iocoder.yudao.module.api.product.sku.dto.ProductPropertyValueRespDTO;
import cn.iocoder.yudao.module.api.product.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.api.product.spu.ProductSpuApi;
import cn.iocoder.yudao.module.api.product.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.api.promotion.combination.CombinationRecordApi;
import cn.iocoder.yudao.module.api.promotion.combination.dto.CombinationRecordRespDTO;
import cn.iocoder.yudao.module.api.promotion.combination.enums.CombinationRecordStatusEnum;
import cn.iocoder.yudao.module.api.promotion.groupon.dto.GrouponPackageRespDTO;
import cn.iocoder.yudao.module.api.shop.info.ShopInfoApi;
import cn.iocoder.yudao.module.api.shop.info.dto.ShopInfoDTO;
import cn.iocoder.yudao.module.api.shop.staff.ShopStaffApi;
import cn.iocoder.yudao.module.api.shop.staff.enums.ShopStaffStatusEnum;
import cn.iocoder.yudao.module.api.shop.staff.vo.ShopStaffVO;
import cn.iocoder.yudao.module.api.trade.aftersale.enums.AfterSaleStatusEnum;
import cn.iocoder.yudao.module.api.trade.delivery.enums.DeliveryTypeEnum;
import cn.iocoder.yudao.module.api.trade.order.dto.*;
import cn.iocoder.yudao.module.api.trade.order.enums.*;
import cn.iocoder.yudao.module.api.trade.verify.dto.TradeVerifyRecordSaveDTO;
import cn.iocoder.yudao.module.api.trade.verify.enums.VerifyBizType;
import cn.iocoder.yudao.module.trade.aftersale.dal.dataobject.AfterSaleDO;
import cn.iocoder.yudao.module.trade.aftersale.dal.mysql.AfterSaleMapper;
import cn.iocoder.yudao.module.trade.cart.dal.dataobject.CartDO;
import cn.iocoder.yudao.module.trade.cart.service.CartService;
import cn.iocoder.yudao.module.trade.delivery.dal.dataobject.DeliveryExpressDO;
import cn.iocoder.yudao.module.trade.delivery.dal.dataobject.DeliveryPickUpStoreDO;
import cn.iocoder.yudao.module.trade.delivery.service.DeliveryExpressService;
import cn.iocoder.yudao.module.trade.delivery.service.DeliveryPickUpStoreService;
import cn.iocoder.yudao.module.trade.framework.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.order.bo.TradeOrderLogCreateBO;
import cn.iocoder.yudao.module.trade.order.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.order.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.order.convert.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.order.dal.dataobject.TradeOrderDO;
import cn.iocoder.yudao.module.trade.order.dal.dataobject.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.order.dal.dataobject.TradeOrderLogDO;
import cn.iocoder.yudao.module.trade.order.dal.mysql.TradeOrderItemMapper;
import cn.iocoder.yudao.module.trade.order.dal.mysql.TradeOrderMapper;
import cn.iocoder.yudao.module.trade.order.mq.TradeOrderProducer;
import cn.iocoder.yudao.module.trade.order.service.calculator.TradePriceCalculatorHelper;
import cn.iocoder.yudao.module.trade.order.service.handler.TradeOrderHandler;
import cn.iocoder.yudao.module.trade.order.vo.*;
import cn.iocoder.yudao.module.trade.verify.service.TradeVerifyRecordService;
import com.github.yulichang.toolkit.StrUtils;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getTerminal;
import static cn.iocoder.yudao.module.api.infrastructure.ConfigConstants.CONFIG_MEMBER;
import static cn.iocoder.yudao.module.api.pay.ConfigConstants.PAY_TIMEOUT_MINUTES;
import static cn.iocoder.yudao.module.api.pay.ErrorCodeConstants.PAY_ORDER_NOT_FOUND;
import static cn.iocoder.yudao.module.api.trade.ConfigConstants.TRADE_ORDER_NO_PREFIX;
import static cn.iocoder.yudao.module.api.trade.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.api.trade.MessageTemplateConstants.WXA_ORDER_DELIVERY;

/**
 * 交易订单【写】ServiceImpl 实现类
 *
 * @author LeeYan9
 * @since 2022-08-26
 */
@Service
@Slf4j
public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {

    @Resource
    private TradeOrderMapper tradeOrderMapper;
    @Resource
    private TradeOrderItemMapper tradeOrderItemMapper;
    @Resource
    private AfterSaleMapper afterSaleMapper;

    @Resource
    private SerialNoRedisRepository serialNoRedisRepository;
    @Resource
    private List<TradeOrderHandler> tradeOrderHandlers;
    @Resource
    private TradeOrderProperties tradeOrderProperties;
    @Resource
    private TradeOrderProducer tradeOrderProducer;

    @Resource
    private TradeOrderLogService tradeOrderLogService;
    @Resource
    private TradeVerifyRecordService tradeVerifyRecordService;
    @Resource
    private CartService cartService;
    @Resource
    private TradePriceService tradePriceService;
    @Resource
    private DeliveryExpressService deliveryExpressService;
    @Resource
    private DeliveryPickUpStoreService pickUpStoreService;

    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ShopInfoApi shopInfoApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private PayOrderApi payOrderApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    public PayRefundApi payRefundApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private MemberAddressApi memberAddressApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ProductSpuApi productSpuApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ProductSkuApi productSkuApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ProductEvaluationApi productEvaluationApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    public SocialClientApi socialClientApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private NotifyMessageSendApi notifyMessageSendApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ConfigApiV2 configApiV2;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private MemberLevelApi memberLevelApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ShopStaffApi shopStaffApi;
    @Resource
    private CombinationRecordApi combinationRecordApi;

    // =================== Order ===================

    @Override
    public AppTradeOrderSettlementRespVO settlementOrder(Long userId, AppTradeOrderSettlementReqVO settlementReqVO) {
        if (settlementReqVO.getDeliveryType() == null) {
            settlementReqVO.setDeliveryType(DeliveryTypeEnum.NONE.getType());
        }
        // 1. 获得收货地址（快递发货、同城配送、上门服务需要用到）
        MemberAddressRespDTO address = getAddress(userId, settlementReqVO.getAddressId(), settlementReqVO.getDineMode());
        if (address != null) {
            settlementReqVO.setAddressId(address.getId());
        }
        // 2. 计算价格
        TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, settlementReqVO);
        // 3. 拼接返回
        MemberConfigRespDTO config = configApiV2.getConfig(CONFIG_MEMBER, MemberConfigRespDTO.class);
        int pointDeductUnitPrice = 0;
        if (!config.isPointDeductDisable()) {
            pointDeductUnitPrice = config.getPointTradeDeductUnitPrice();
        }
        int cryptocurrencyDeductUnitPrice = config.getCryptocurrencyTradeDeductUnitPrice();
        int discountPercent = memberLevelApi.getMemberLevelDiscountPercentByUserId(getLoginUserId());
        AppTradeOrderSettlementRespVO respVO = TradeOrderConvert.INSTANCE.convert(calculateRespBO, address);
        for (AppTradeOrderSettlementItemRespVO item : respVO.getItems()) {
            item.setPointDeductUnitPrice(pointDeductUnitPrice);
            item.setCryptocurrencyDeductUnitPrice(cryptocurrencyDeductUnitPrice);
            item.setMemberLevelPrice(calculateMemberLevelPrice(discountPercent, item.getPrice()));
        }
        // 4. 获得店铺信息（外卖/外送产品到店自取、到店核销需要展示）
        ShopInfoDTO shopInfo = shopInfoApi.getShopInfo(calculateRespBO.getShopId());
        respVO.setShop(shopInfo);
        return respVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public OrderSubmitVO createGiftTradeOrder(Long userId, GiftTradeOrderSaveDTO saveDTO) {
        int originPrice = saveDTO.getOriginPrice();
        ProductSkuRespDTO skuRespDTO = null;
        ProductSpuRespDTO spuRespDTO = null;
        if (saveDTO.getSkuId() != null && saveDTO.getSkuId() > 0) {
            skuRespDTO = productSkuApi.validateSku(saveDTO.getSkuId());
            originPrice = skuRespDTO.getPrice();
            spuRespDTO = productSpuApi.validateSpu(skuRespDTO.getSpuId());
        }
        TradeOrderDO order = new TradeOrderDO();
        order.setUserId(userId);
        order.setShopId(saveDTO.getShopId());
        order.setType(TradeOrderTypeEnum.GIFT.getType());
        order.setTotalPrice(originPrice);
        order.setSettlementPrice(saveDTO.getCostPrice());
        order.setPayPrice(0);
        order.setAdjustPrice(0);
        order.setCouponPrice(0);
        order.setDeliveryPrice(0);
        order.setDiscountPrice(originPrice);
        order.setVipPrice(0);
        order.setDeductPoint(0);
        order.setPointPrice(0);
        order.setGivePoint(0);
        order.setDeductCryptocurrency(0D);
        order.setCryptocurrencyPrice(0);
        order.setGiveCryptocurrency(0D);
        order.setGiveBonus(0D);
        order.setGiveCouponTemplateCounts(Map.of());
        order.setNo(serialNoRedisRepository.generateNo(TRADE_ORDER_NO_PREFIX));
        order.setStatus(TradeOrderStatusEnum.DOING.getStatus());
        order.setProductCount(saveDTO.getCount());
        order.setUserIp(getClientIP());
        order.setTerminal(getTerminal());
        order.setPayStatus(true);
        order.setPayTime(LocalDateTime.now());
        order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
        order.setRefundPrice(0);
        order.setDeliveryType(saveDTO.getDeliveryType());
        if (saveDTO.getAddressId() != null && saveDTO.getAddressId() > 0) {
            MemberAddressRespDTO address = memberAddressApi.getAddress(saveDTO.getAddressId(), userId);
            if (Objects.equals(saveDTO.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getType()) ||
                Objects.equals(saveDTO.getDeliveryType(), DeliveryTypeEnum.TAKE_OUT.getType())) {
                Assertions.notNull(address, "地址(%s})不存在".formatted(saveDTO.getAddressId())); // 价格计算时，已经计算
            }
            order.setReceiverName(address.getName());
            order.setReceiverMobile(address.getMobile());
            order.setReceiverAreaId(address.getAreaId());
            order.setReceiverDetailAddress(address.getDetailAddress());
        }
        if (List.of(DeliveryTypeEnum.PICK_UP.getType(), DeliveryTypeEnum.VERIFY.getType(), DeliveryTypeEnum.ONSITE.getType())
                .contains(saveDTO.getDeliveryType())) {
            // 需要核销的订单，随机一个核销码，长度为 8 位数
            order.setVerifyCode(RandomUtil.randomNumbers(8));
        }
        order.setRemark("赠品");
        tradeOrderMapper.insert(order);
        TradeOrderItemDO orderItem = new TradeOrderItemDO();
        orderItem.setUserId(userId);
        orderItem.setShopId(saveDTO.getShopId());
        orderItem.setOrderId(order.getId());
        orderItem.setPrice(originPrice);
        orderItem.setSettlementPrice(saveDTO.getCostPrice());
        orderItem.setSpuName(saveDTO.getName());
        orderItem.setPicUrl(saveDTO.getPicUrl());
        if (spuRespDTO != null) {
            orderItem.setSpuId(spuRespDTO.getId());
            orderItem.setSkuId(skuRespDTO.getId());
            orderItem.setProperties(skuRespDTO.getProperties());
        } else {
            orderItem.setSpuId(0L);
            orderItem.setSkuId(0L);
            orderItem.setProperties(List.of(ProductPropertyValueRespDTO.DEFAULT));
        }
        orderItem.setCount(saveDTO.getCount());
        orderItem.setDiscountPrice(originPrice);
        orderItem.setCouponPrice(0);
        orderItem.setDeliveryPrice(0);
        orderItem.setPayPrice(0);
        orderItem.setVipPrice(0);
        orderItem.setPointPrice(0);
        orderItem.setDeductPoint(0);
        orderItem.setGivePoint(0);
        orderItem.setCryptocurrencyPrice(0);
        orderItem.setDeductCryptocurrency(0);
        orderItem.setGiveCryptocurrency(0);
        orderItem.setAfterSaleStatus(AfterSaleStatusEnum.NONE.getStatus());
        orderItem.setCommentStatus(false);
        List<TradeOrderItemDO> orderItems = List.of(orderItem);
        tradeOrderItemMapper.insertBatch(orderItems);
        String payNo = createPayOrder(order, orderItems, null);
        TradeOrderRespDTO orderRespDTO = TradeOrderConvert.INSTANCE.convert(order);
        orderRespDTO.setPayNo(payNo);
        List<TradeOrderItemRespDTO> orderItemRespDTOS = TradeOrderConvert.INSTANCE.convertList(orderItems);
        tradeOrderProducer.sendTradeOrderCreatedMessage(orderRespDTO, orderItemRespDTOS);
        OrderSubmitVO respVO = new OrderSubmitVO();
        respVO.setTradeNo(order.getNo());
        respVO.setPrice(order.getPayPrice());
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.MEMBER_CREATE, null, order.getStatus(), null);
        return respVO;
    }

    private int calculateMemberLevelPrice(int discountPercent, int price) {
        if (discountPercent <= 0) {
            return price;
        }
        int newPrice = price * discountPercent / 100;
        return price - newPrice;
    }

    /**
     * 获得用户地址
     *
     * @param userId    用户编号
     * @param addressId 地址编号
     * @param dineMode 点餐模式
     * @return 地址
     */
    private MemberAddressRespDTO getAddress(Long userId, @Nullable Long addressId, @Nullable Boolean dineMode) {
        if (addressId != null && addressId > 0) {
            return memberAddressApi.getAddress(addressId, userId);
        }
        if (Boolean.TRUE.equals(dineMode)) {
            return memberAddressApi.getDineAddress(userId);
        }
        return memberAddressApi.getDefaultAddress(userId);
    }

    /**
     * 计算订单价格
     *
     * @param userId          用户编号
     * @param reqVO 结算信息
     * @return 订单价格
     */
    private TradePriceCalculateRespBO calculatePrice(Long userId, AppTradeOrderSettlementReqVO reqVO) {
        // 如果来自购物车，则获得购物车的产品
        Set<Long> cartIds = convertSet(reqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
        List<CartDO> cartList = cartService.getCartList(userId, cartIds);
        // 计算价格
        TradePriceCalculateReqBO calculateReqBO = TradeOrderConvert.INSTANCE.convert(userId, reqVO, cartList);
        List<TradePriceCalculateReqBO.Item> items = calculateReqBO.getItems();
        if (CollUtil.isNotEmpty(items)) {
            // 防御性编程，保证都是选中的
            for (TradePriceCalculateReqBO.Item item : items) {
                Assertions.isTrue(item.getSelected(), "含有未设置为选中的产品");
                Assertions.isTrue(item.getCount() > 0, "含有数量无效的产品");
            }
        }
        return tradePriceService.calculateOrderPrice(calculateReqBO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public TradeOrderDO createOrder(Long userId, AppTradeOrderCreateReqVO createReqVO) {
        // 1.0 价格计算
        TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO);
        // 1.1 结果校验
        if (CollUtil.isNotEmpty(calculateRespBO.getSupportDeliveryTypes())) {
            if (createReqVO.getDeliveryType() == null ||
                Objects.equals(createReqVO.getDeliveryType(), DeliveryTypeEnum.NONE.getType())) {
                createReqVO.setDeliveryType(calculateRespBO.getSupportDeliveryTypes().get(0));
            }
            Assertions.isTrue(calculateRespBO.getSupportDeliveryTypes().contains(createReqVO.getDeliveryType()), "该商家不支持所选的配送方式");
        }
        if (Boolean.TRUE.equals(createReqVO.getUsePointDeduction())) {
            Assertions.isTrue(calculateRespBO.getDeductPoint() == calculateRespBO.getPrice().getTotalPointDeductCount(), "积分不足以抵扣");
        }
        if (Boolean.TRUE.equals(createReqVO.getFullCryptocurrencyDeduction())) {
            Assertions.isTrue(calculateRespBO.getDeductCryptocurrency().intValue() == calculateRespBO.getPrice().getTotalCryptocurrencyDeductCount(), "联盟豆不足以全额兑换");
        }
        if (Boolean.TRUE.equals(createReqVO.getUseCryptocurrencyDeduction())) {
            Assertions.isTrue(calculateRespBO.getDeductCryptocurrency().doubleValue() > 0, "还没有联盟豆，无法抵扣");
        }
        if (Boolean.TRUE.equals(createReqVO.getUseVipPrice())) {
            Assertions.isTrue(Boolean.TRUE.equals(calculateRespBO.getValidVip()), "还不是VIP用户");
        }
        if (Boolean.TRUE.equals(createReqVO.getUseWholesalePrice())) {
            Assertions.isTrue(Boolean.TRUE.equals(calculateRespBO.getValidMerchantVip()), "还不是VIP商家");
        }
        // 1.2 构建订单
        TradeOrderDO order = buildTradeOrder(userId, createReqVO, calculateRespBO);
        List<TradeOrderItemDO> orderItems = TradeOrderConvert.INSTANCE.convertList(order, calculateRespBO);
        // 2. 订单创建前的逻辑
        for (TradeOrderHandler handler : tradeOrderHandlers) {
            try {
                handler.beforeOrderCreate(order, orderItems);
            } catch (KnownServiceException ex) {
                log.error("[beforeCreateTradeOrder][handler({})] 执行异常：{}", handler.getClass().getName(), ex.getMessage());
                throw ex; // 继续抛出异常，任意一个回调有异常都进行事务回滚
            } catch (Throwable ex) {
                log.error("[beforeCreateTradeOrder][handler({})] 执行异常", handler.getClass().getName(), ex);
                throw ex; // 继续抛出异常，任意一个回调有异常都进行事务回滚
            }
        }
        // 3.1 保存订单
        tradeOrderMapper.insert(order);
        // 3.2 防御性编程：避免订单没有订单项报错
        if (CollUtil.isNotEmpty(orderItems)) {
            orderItems.forEach(orderItem -> orderItem.setOrderId(order.getId()));
            tradeOrderItemMapper.insertBatch(orderItems);
        }
        // 4.1. 执行订单创建后置处理器
        for (TradeOrderHandler handler : tradeOrderHandlers) {
            try {
                handler.afterOrderCreate(order, orderItems);
            } catch (KnownServiceException ex) {
                log.error("[afterCreateTradeOrder][handler({})] 执行异常：{}", handler.getClass().getName(), ex.getMessage());
                throw ex; // ！！！继续抛出异常，任意一个回调有异常都进行事务回滚
            } catch (Throwable ex) {
                log.error("[afterCreateTradeOrder][handler({})] 执行异常", handler.getClass().getName(), ex);
                throw ex; // ！！！继续抛出异常，任意一个回调有异常都进行事务回滚
            }
        }
        // 4.2. 删除购物车产品
        Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
        if (CollUtil.isNotEmpty(cartIds)) {
            cartService.deleteCart(order.getUserId(), cartIds);
        }
        // 4.3. 插入订单日志
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.MEMBER_CREATE, null, order.getStatus(), null);
        // 4.4. 通知其他模块进行订单创建后操作
        TradeOrderRespDTO orderRespDTO = TradeOrderConvert.INSTANCE.convert(order);
        String payNo = createPayOrder(order, orderItems, calculateRespBO.getGrouponPackage());
        orderRespDTO.setPayNo(payNo);
        List<TradeOrderItemRespDTO> orderItemRespDTOS = TradeOrderConvert.INSTANCE.convertList(orderItems);
        tradeOrderProducer.sendTradeOrderCreatedMessage(orderRespDTO, orderItemRespDTOS);
        return order;
    }

    private TradeOrderDO buildTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
                                         TradePriceCalculateRespBO calculateRespBO) {
        TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, createReqVO, calculateRespBO);
        if (StrUtils.isNotBlank(createReqVO.getReservePeriod()) && createReqVO.getReserveDate() == null) {
            // 默认预约第二日的时间段
            order.setReserveDate(LocalDate.now().plusDays(1));
        }
        order.setShopId(calculateRespBO.getShopId());
        order.setType(calculateRespBO.getType());
        order.setNo(serialNoRedisRepository.generateNo(TRADE_ORDER_NO_PREFIX));
        order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
        order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
        order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
        order.setUserIp(getClientIP())
        ;
        order.setTerminal(getTerminal());
        // 使用 + 赠送优惠券
        order.setGiveCouponTemplateCounts(calculateRespBO.getGiveCouponTemplateCounts());
        // 支付 + 退款信息
        order.setAdjustPrice(0);
        order.setPayStatus(false);
        order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
        order.setRefundPrice(0);
        // 物流信息
        order.setDeliveryType(createReqVO.getDeliveryType());
        order.setReceiverName(createReqVO.getContactName());
        order.setReceiverMobile(createReqVO.getContactMobile());
        if (createReqVO.getAddressId() != null && createReqVO.getAddressId() > 0) {
            MemberAddressRespDTO address = memberAddressApi.getAddress(createReqVO.getAddressId(), userId);
            if (Objects.equals(createReqVO.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getType()) ||
                Objects.equals(createReqVO.getDeliveryType(), DeliveryTypeEnum.TAKE_OUT.getType())) {
                Assertions.notNull(address, "地址(%s})不存在".formatted(createReqVO.getAddressId())); // 价格计算时，已经计算
                Assertions.notBlank(address.getName(), "联系人名称不能为空");
                Assertions.notBlank(address.getMobile(), "联系电话不能为空");
            }
            order.setReceiverName(address.getName());
            order.setReceiverMobile(address.getMobile());
            order.setReceiverAreaId(address.getAreaId());
            order.setReceiverDetailAddress(address.getDetailAddress());
        }
        if (List.of(DeliveryTypeEnum.PICK_UP.getType(), DeliveryTypeEnum.VERIFY.getType(), DeliveryTypeEnum.ONSITE.getType())
                .contains(createReqVO.getDeliveryType())) {
            // 需要核销的订单，随机一个核销码，长度为 8 位数
            order.setVerifyCode(RandomUtil.randomNumbers(8));
        }
        order.setDinePeoples(calculateRespBO.getDinePeoples());
        return order;
    }

    private String createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems, GrouponPackageRespDTO grouponPackage) {
        // 创建支付单，用于后续的支付
        PayOrderCreateDTO createDTO = new PayOrderCreateDTO();
        // 商户相关字段
        createDTO.setTradeNo(order.getNo());
        String prefix = "购买";
        if (Objects.equals(order.getType(), TradeOrderTypeEnum.GROUPON.getType())) {
            createDTO.setSubject(BillTypeEnum.PAY_GROUPON_PACKAGE.getName());
        } else if (Objects.equals(order.getType(), TradeOrderTypeEnum.GIFT.getType())) {
            createDTO.setSubject(BillTypeEnum.EXCHANGE_PRODUCT.getName());
            prefix = "兑换";
        } else {
            createDTO.setSubject(BillTypeEnum.PAY_PRODUCT.getName());
        }
        String body;
        if (grouponPackage != null) {
            body = prefix + "团购套餐【" + grouponPackage.getName() + "】";
        } else if (CollUtil.isEmpty(orderItems)) {
            body = "没有产品明细";
        } else {
            body = prefix + "产品【" + orderItems.stream().map(TradeOrderItemDO::getSpuName).collect(Collectors.joining("#")) + "】";
        }
        createDTO.setBody(body);
        createDTO.setPrice(order.getPayPrice());
        return payOrderApi.createOrder(createDTO);
    }

    @Override
    public void updateOrderExpired(String tradeNo) {
        // 1.1 校验订单是否存在
        TradeOrderDO order = validateOrderExists(tradeNo);
        // 1.2 校验订单已支付
        if (TradeOrderStatusEnum.isClosed(order.getStatus())) {
            // 幂等性：如果订单已取消，说明重复回调
            return;
        }
        getSelf().cancelOrderBySystem(order);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateOrderPaid(String tradeNo, String payNo) {
        // 1.1 校验订单是否存在
        TradeOrderDO order = validateOrderExists(tradeNo);
        // 1.2 校验订单已支付
        if (!TradeOrderStatusEnum.isUnpaid(order.getStatus()) || order.getPayStatus()) {
            // 幂等性：如果订单已支付，说明重复回调
            return;
        }
        // 2. 校验支付订单的合法性
        PayOrderVO orderVO = validatePayOrderPaid(order);
        // 3. 更新 TradeOrderDO 状态为已支付
        TradeOrderDO tradeOrderDO = new TradeOrderDO();
        tradeOrderDO.setStatus(TradeOrderStatusEnum.PAID.getStatus());
        tradeOrderDO.setPayStatus(true);
        tradeOrderDO.setPayNo(payNo);
        tradeOrderDO.setPayTime(LocalDateTime.now());
        tradeOrderDO.setPayChannelCode(orderVO.getChannelCode());
        tradeOrderDO.setDineStatus(DineStatusEnum.PENDING.getValue());
        int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), tradeOrderDO);
        if (updateCount == 0) {
            throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
        }
        TradeOrderRespDTO orderRespDTO = TradeOrderConvert.INSTANCE.convert(order);
        orderRespDTO.setStatus(TradeOrderStatusEnum.PAID.getStatus());
        orderRespDTO.setPayStatus(true);
        orderRespDTO.setPayNo(payNo);
        orderRespDTO.setPayTime(LocalDateTime.now());
        tradeOrderProducer.sendTradeOrderPaidMessage(orderRespDTO);
        // 4. 执行 TradeOrderHandler 的后置处理
        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
        for (TradeOrderHandler handler : tradeOrderHandlers) {
            log.info("updateOrderPaid: tradeNo({}), payNo({}), 回调支付成功后置处理器({})", tradeNo, order.getPayNo(), handler.getClass().getName());
            try {
                handler.afterPayOrder(order, orderItems);
            } catch (Throwable ex) {
                log.error("[updateOrderPaid][handler({})] 执行异常", handler.getClass().getName(), ex);
                // ！！！捕获异常不继续抛出，即使有个别的回调异常，也不让事务回滚
            }
        }
        // 5. 记录订单日志
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.MEMBER_PAY, order.getStatus(), TradeOrderStatusEnum.PAID.getStatus(), null);
        // 6. 生活商圈订单自动到待核销状态
        if (order.isO2oType()) {
            TransactionUtils.afterCommit(() -> {
                doO2oTypeOrder(order.getId(), TradeOrderStatusEnum.PAID.getStatus());
                return null;
            });
        }
    }

    private void doO2oTypeOrder(Long orderId, Integer orderStatus) {
        // 更新订单为待核销
        TradeOrderDO updateOrderObj = new TradeOrderDO();
        updateOrderObj.setLogisticsId(TradeOrderDO.LOGISTICS_ID_NULL);
        updateOrderObj.setLogisticsNo("");
        // 执行更新
        updateOrderObj.setStatus(TradeOrderStatusEnum.DOING.getStatus());
        updateOrderObj.setDeliveryTime(LocalDateTime.now());
        tradeOrderMapper.updateByIdAndStatus(orderId, orderStatus, updateOrderObj);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateOrderRefunded(String no) {
        // 1.1 校验售后是否存在
        AfterSaleDO afterSale = afterSaleMapper.selectByNo(no);
        if (afterSale == null) {
            log.warn("[trade][onMessage][售后订单({})不存在]", no);
            return;
        }
        // 1.2 校验售后已完成
        if (Objects.equals(afterSale.getStatus(), AfterSaleStatusEnum.COMPLETE.getStatus())) {
            // 如果售后已完成，且支付单号相同，直接返回，说明重复回调
            return;
        }
        // 2. 校验售后的合法性
        validatePayOrderRefunded(afterSale);
        // 3 校验交易订单是否存在
        TradeOrderDO tradeOrderDO = tradeOrderMapper.selectById(afterSale.getOrderId());
        if (tradeOrderDO == null) {
            return;
        }
        saveTradeOrderLog(afterSale.getOrderId(), TradeOrderOperateTypeEnum.MEMBER_REFUND, tradeOrderDO.getStatus(), TradeOrderStatusEnum.CLOSED.getStatus(), null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void syncOrderPayStatusQuietly(String tradeNo) {
        TradeOrderDO tradeOrderDO = tradeOrderMapper.selectByNo(tradeNo);
        if (tradeOrderDO == null) {
            return;
        }
        PayOrderVO orderVO = payOrderApi.getOrderByTradeNo(tradeNo);
        if (orderVO == null) {
            return;
        }
        if (UnifiedOrderStatusEnum.isSuccess(orderVO.getStatus())) {
            updateOrderPaid(tradeNo, orderVO.getNo());
        }
    }

    /**
     * 校验支付订单的合法性
     *
     * @param order      交易订单
     * @return 支付订单
     */
    private PayOrderVO validatePayOrderPaid(TradeOrderDO order) {
        // 1. 校验支付单是否存在
        PayOrderVO orderVO = payOrderApi.getOrderByTradeNo(order.getNo());
        if (orderVO == null) {
            log.error("[validatePayOrderPaid][tradeNo({}) 不存在，请进行排查处理！]", order.getNo());
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        // 2.1 校验支付单已支付
        if (!UnifiedOrderStatusEnum.isSuccess(orderVO.getStatus())) {
            log.error("[validatePayOrderPaid][tradeNo({}) 未支付，请进行排查处理！支付单数据是：{}]", order.getNo(), JsonUtils.toJsonString(orderVO));
            throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
        }
        // 2.2 校验支付订单匹配
        if (order.getPayNo() != null && ObjectUtil.notEqual(orderVO.getNo(), order.getPayNo())) {
            log.error("[validatePayOrderPaid][tradeNo({}) 支付单号不匹配，请进行排查处理！交易单的是：{}，支付单的是：{}]", order.getNo(), order.getPayNo(), orderVO.getNo());
            throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
        }
        // 2.1 校验支付金额一致
        if (ObjectUtil.notEqual(orderVO.getPrice(), order.getPayPrice())) {
            log.error("[validatePayOrderPaid][tradeNo({}) payNo({}) 支付金额不匹配，请进行排查处理！交易单的金额是：{}，支付单的金额是：{}]", order.getNo(), order.getPayNo(), order.getPayPrice(), orderVO.getPrice());
            throw exception(ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
        }
        return orderVO;
    }

    /**
     * 校验退款订单的合法性
     *
     * @param afterSale 售后订单
     */
    private void validatePayOrderRefunded(AfterSaleDO afterSale) {
        // 1. 校验退款单是否存在
        PayRefundVO refundVO = payRefundApi.getRefund(afterSale.getPayRefundId());
        if (refundVO == null) {
            log.error("[validatePayOrderRefunded][afterSale({}) payRefundId({}) 不存在，请进行处理！]", afterSale.getId(), afterSale.getPayRefundId());
            throw exception(AFTER_SALE_NOT_FOUND);
        }
        // 2.1 校验退款单已退款
        if (!UnifiedRefundStatusEnum.isSuccess(refundVO.getStatus())) {
            log.error("[validatePayOrderRefunded][afterSale({}) payRefundId({}) 未退款成功，请进行处理！退款单数据是：{}]", afterSale.getId(), afterSale.getPayRefundId(), JsonUtils.toJsonString(refundVO));
            throw exception(AFTER_SALE_REFUND_FAIL_STATUS_NOT_REFUND_SUCCESS);
        }
        // 2.2 校验退款金额一致
        if (ObjectUtil.notEqual(refundVO.getRefundPrice(), afterSale.getRefundPrice())) {
            log.error("[validatePayOrderRefunded][afterSale({}) payRefundId({}) 退款金额不匹配，请进行处理！售后单数据是：{}，退款单数据是：{}]", afterSale.getId(), afterSale.getPayRefundId(), JsonUtils.toJsonString(afterSale), JsonUtils.toJsonString(refundVO));
            throw exception(AFTER_SALE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
        }
        // 2.2 校验退款单匹配（二次）
        if (ObjectUtil.notEqual(refundVO.getRefundNo(), afterSale.getNo())) {
            log.error("[validatePayOrderRefunded][afterSale({}) 退款单不匹配({})，请进行处理！退款单数据是：{}]", afterSale.getNo(), refundVO.getRefundNo(), JsonUtils.toJsonString(refundVO));
            throw exception(AFTER_SALE_REFUND_FAIL_REFUND_ID_ERROR);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deliveryOrder(TradeOrderDeliveryDTO dto) {
        // 校验并获得交易订单（可发货）
        TradeOrderDO order = validateOrderDeliverable(dto.getId());
        doDeliveryOrder(dto, order);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deliveryOrder(Long shopId, TradeOrderDeliveryDTO dto) {
        // 校验并获得交易订单（可发货）
        TradeOrderDO order = validateOrderDeliverable(dto.getId());
        if (!Objects.equals(order.getShopId(), shopId)) {
            throw exception(FORBIDDEN);
        }
        doDeliveryOrder(dto, order);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void acceptOrder(Long shopId, Long id) {
        // 校验并获得交易订单（可接单）
        TradeOrderDO order = validateOrderAcceptable(id);
        if (!Objects.equals(order.getShopId(), shopId)) {
            throw exception(FORBIDDEN);
        }
        doAcceptOrder(order);
    }

    private TradeOrderDO validateOrderAcceptable(Long id) {
        TradeOrderDO order = validateOrderExists(id);
        if (!Objects.equals(DineStatusEnum.PENDING.getValue(), order.getDineStatus())) {
            throw new KnownServiceException("接单失败：订单不是【待接单】状态");
        }
        if (!Objects.equals(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
            throw new KnownServiceException("接单失败：订单已退款或部分退款");
        }
        return order;
    }

    private void doAcceptOrder(TradeOrderDO order) {
        TradeOrderDO updateOrderObj = new TradeOrderDO();
        updateOrderObj.setId(order.getId());
        updateOrderObj.setDineStatus(DineStatusEnum.ACCEPTED.getValue());
        updateOrderObj.setAcceptedTime(LocalDateTime.now());
        int updateCount = tradeOrderMapper.updateById(updateOrderObj);
        Assertions.isTrue(updateCount > 0, "接单失败");
        // 记录订单日志
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.ADMIN_ACCEPT, order.getStatus(), order.getStatus(), MapUtil.<String, Object>builder().build());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void packOrder(Long shopId, Long id) {
        // 校验并获得交易订单（可出餐）
        TradeOrderDO order = validateOrderPackable(id);
        if (!Objects.equals(order.getShopId(), shopId)) {
            throw exception(FORBIDDEN);
        }
        doPackOrder(order);
    }

    private TradeOrderDO validateOrderPackable(Long id) {
        TradeOrderDO order = validateOrderExists(id);
        if (!(Objects.equals(DineStatusEnum.ACCEPTED.getValue(), order.getDineStatus()) ||
              Objects.equals(DineStatusEnum.ASSIGNED.getValue(), order.getDineStatus()))) {
            throw new KnownServiceException("出餐失败：订单不是【已接单/已派单】状态");
        }
        if (!Objects.equals(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
            throw new KnownServiceException("出餐失败：订单已退款或部分退款");
        }
        return order;
    }

    private void doPackOrder(TradeOrderDO order) {
        TradeOrderDO updateOrderObj = new TradeOrderDO();
        updateOrderObj.setId(order.getId());
        updateOrderObj.setDineStatus(DineStatusEnum.PACKED.getValue());
        updateOrderObj.setPackedTime(LocalDateTime.now());
        int updateCount = tradeOrderMapper.updateById(updateOrderObj);
        Assertions.isTrue(updateCount > 0, "出餐失败");
        // 记录订单日志
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.ADMIN_PACK, order.getStatus(), order.getStatus(), MapUtil.<String, Object>builder().build());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void pickupOrderByRider(Long userId, Long id) {
        // 校验并获得交易订单（可取餐）
        TradeOrderDO order = validateOrderPickupable(id);
        ShopStaffVO staffVO = shopStaffApi.getById(order.getRiderStaffId());
        if (staffVO == null || !Objects.equals(staffVO.getMemberUserId(), userId)) {
            throw exception(FORBIDDEN);
        }
        doPickupOrder(order);
    }

    private TradeOrderDO validateOrderPickupable(Long id) {
        TradeOrderDO order = validateOrderExists(id);
        if (!Objects.equals(DineStatusEnum.PACKED.getValue(), order.getDineStatus())) {
            throw new KnownServiceException("取餐失败：订单不是【已出餐】状态");
        }
        if (!Objects.equals(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
            throw new KnownServiceException("取餐失败：订单已退款或部分退款");
        }
        return order;
    }

    private void doPickupOrder(TradeOrderDO order) {
        TradeOrderDO updateOrderObj = new TradeOrderDO();
        updateOrderObj.setId(order.getId());
        updateOrderObj.setStatus(TradeOrderStatusEnum.DOING.getStatus());
        updateOrderObj.setDineStatus(DineStatusEnum.DELIVERING.getValue());
        updateOrderObj.setDeliveryTime(LocalDateTime.now());
        int updateCount = tradeOrderMapper.updateById(updateOrderObj);
        Assertions.isTrue(updateCount > 0, "取餐失败");
        // 记录订单日志
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.RIDER_PICKUP, order.getStatus(), TradeOrderStatusEnum.DOING.getStatus(), MapUtil.<String, Object>builder().build());
    }

    private void doDeliveryOrder(TradeOrderDeliveryDTO dto, TradeOrderDO order) {
        // 2. 更新订单为已发货，即买家待收货
        TradeOrderDO updateOrderObj = new TradeOrderDO();
        // 2.1 快递发货
        DeliveryExpressDO express = null;
        if (ObjectUtil.notEqual(dto.getLogisticsId(), TradeOrderDO.LOGISTICS_ID_NULL)) {
            express = deliveryExpressService.validateDeliveryExpress(dto.getLogisticsId());
            updateOrderObj.setLogisticsId(dto.getLogisticsId());
            updateOrderObj.setLogisticsNo(dto.getLogisticsNo());
        } else {
            // 2.2 无需发货
            updateOrderObj.setLogisticsId(TradeOrderDO.LOGISTICS_ID_NULL);
            updateOrderObj.setLogisticsNo("");
        }
        updateOrderObj.setRiderStaffId(dto.getRiderStaffId());
        // 执行更新
        updateOrderObj.setStatus(TradeOrderStatusEnum.DOING.getStatus());
        updateOrderObj.setDeliveryTime(LocalDateTime.now());
        int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), updateOrderObj);
        if (updateCount == 0) {
            throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED);
        }
        // 3. 记录订单日志
        String expressName = express != null ? express.getName() : "无";
        String logisticsNo = express != null ? dto.getLogisticsNo() : "无";
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.ADMIN_DELIVERY, order.getStatus(), TradeOrderStatusEnum.DOING.getStatus(), MapUtil.<String, Object>builder().put("deliveryName", expressName).put("logisticsNo", logisticsNo).build());
        tradeOrderProducer.sendTradeOrderDeliveryMessage(TradeOrderConvert.INSTANCE.convert(order));
        // 4.1 发送站内信
        Map<String, Object> msgMap = new HashMap<>(2);
        msgMap.put("no", order.getNo());
        msgMap.put("deliveryName", expressName);
        msgMap.put("logisticsNo", logisticsNo);
        NotifySendSingleToUserReqDTO sendSingleToUserReqDTO = new NotifySendSingleToUserReqDTO()
                .setUserId(order.getUserId())
                .setTemplateCode(TradeNotifySceneEnum.ORDER_DELIVERY.getTemplateCode())
                .setTemplateParams(msgMap);
        try {
            notifyMessageSendApi.sendSingleMessageToMember(sendSingleToUserReqDTO);
        } catch (Exception e) {
            log.error("[doDeliveryOrder][vo({}) 给对应的用户发送站内信失败]", JsonUtils.toJsonString(sendSingleToUserReqDTO), e);
        }
        // 4.2 发送订阅消息
        sendDeliveryOrderMessage(order, dto);
        // 5 处理订单发货后逻辑
        for (TradeOrderHandler handler : tradeOrderHandlers) {
            try {
                handler.afterDeliveryOrder(order);
            } catch (KnownServiceException ex) {
                log.error("[doDeliveryOrder][handler({})] 执行异常：{}", handler.getClass().getName(), ex.getMessage());
                throw ex; // 继续抛出异常，任意一个回调有异常都进行事务回滚
            } catch (Throwable ex) {
                log.error("[doDeliveryOrder][handler({})] 执行异常", handler.getClass().getName(), ex);
                throw ex; // 继续抛出异常，任意一个回调有异常都进行事务回滚
            }
        }
    }

    @Async
    public void sendDeliveryOrderMessage(TradeOrderDO order, TradeOrderDeliveryDTO dto) {
        // 构建并发送模版消息
        Long orderId = order.getId();
        socialClientApi.sendWxaSubscribeMessage(new SocialWxaSubscribeMessageSendReqDTO()
                .setUserId(order.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue())
                .setTemplateTitle(WXA_ORDER_DELIVERY)
                .setPage("pages/order/detail?id=" + orderId) // 订单详情页
                .addMessage("character_string3", String.valueOf(orderId)) // 订单编号
                .addMessage("phrase6", TradeOrderStatusEnum.DOING.getName()) // 订单状态
                .addMessage("date4", LocalDateTimeUtil.formatNormal(LocalDateTime.now()))// 发货时间
                .addMessage("character_string5", StrUtil.blankToDefault(dto.getLogisticsNo(), "-")) // 快递单号
                .addMessage("thing9", order.getReceiverDetailAddress())); // 收货地址
    }

    /**
     * 校验交易订单满足被发货的条件
     * <p>
     * 1. 交易订单未发货
     *
     * @param id 交易订单编号
     * @return 交易订单
     */
    private TradeOrderDO validateOrderDeliverable(Long id) {
        TradeOrderDO order = validateOrderExists(id);
        // 1.1 校验订单是否未发货
        if (ObjectUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
            throw exception(ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE);
        }
        // 1.2 校验 deliveryType 是否为快递发货，是快递发货才可以发货
        if (ObjectUtil.notEqual(order.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getType())) {
            throw exception(ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS);
        }
        // 2. 执行 TradeOrderHandler 前置处理
        for (TradeOrderHandler handler : tradeOrderHandlers) {
            try {
                handler.beforeDeliveryOrder(order);
            } catch (KnownServiceException ex) {
                log.error("[validateOrderDeliverable][handler({})] 执行异常：{}", handler.getClass().getName(), ex.getMessage());
                throw ex; // 继续抛出异常，任意一个回调有异常都进行事务回滚
            } catch (Throwable ex) {
                log.error("[validateOrderDeliverable][handler({})] 执行异常", handler.getClass().getName(), ex);
                throw ex; // 继续抛出异常，任意一个回调有异常都进行事务回滚
            }
        }
        return order;
    }

    @NotNull
    private TradeOrderDO validateOrderExists(Long id) {
        // 校验订单是否存在
        TradeOrderDO order = tradeOrderMapper.selectById(id);
        if (order == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        return order;
    }

    @NotNull
    private TradeOrderDO validateOrderExists(String tradeNo) {
        // 校验订单是否存在
        TradeOrderDO order = tradeOrderMapper.selectByNo(tradeNo);
        if (order == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        return order;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void receiveOrderByBuyer(Long userId, Long id) {
        // 校验并获得交易订单（可收货）
        TradeOrderDO order = validateOrderReceivable(userId, id);
        // 收货订单
        completeOrder(order, TradeOrderOperateTypeEnum.MEMBER_RECEIVE, null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void receiveOrderByRider(Long userId, TradeOrderArriveDTO dto) {
        // 校验并获得交易订单（可送达）
        TradeOrderDO order = validateOrderArrival(dto.getId());
        ShopStaffVO staffVO = shopStaffApi.getById(order.getRiderStaffId());
        if (staffVO == null || !Objects.equals(staffVO.getMemberUserId(), userId)) {
            throw exception(FORBIDDEN);
        }
        // 送达订单
        completeOrder(order, TradeOrderOperateTypeEnum.RIDER_ARRIVE, dto);
        // 配送人员送达后状态变为空闲中
        shopStaffApi.updateStaffStatus(staffVO.getId(), ShopStaffStatusEnum.IDLE.getValue());
    }

    @Override
    public int receiveOrderBySystem() {
        // 1. 查询过期的待支付订单
        LocalDateTime expireTime = LocalDateTime.now().minus(tradeOrderProperties.getReceiveExpireTime());
        List<TradeOrderDO> orders = tradeOrderMapper.selectListByStatusAndDeliveryTimeLt(
                TradeOrderStatusEnum.DOING.getStatus(), expireTime);
        if (CollUtil.isEmpty(orders)) {
            return 0;
        }
        // 2. 遍历执行，逐个取消
        int count = 0;
        for (TradeOrderDO order : orders) {
            try {
                getSelf().receiveOrderBySystem(order);
                count++;
            } catch (Throwable e) {
                log.error("[receiveOrderBySystem][order({}) 自动收货订单异常]", order.getId(), e);
            }
        }
        return count;
    }

    /**
     * 自动收货单个订单
     *
     * @param order 订单
     */
    @Transactional(rollbackFor = Exception.class)
    public void receiveOrderBySystem(TradeOrderDO order) {
        completeOrder(order, TradeOrderOperateTypeEnum.SYSTEM_RECEIVE, null);
    }

    /**
     * 完成订单的核心实现
     *
     * @param order 订单
     */
    private void completeOrder(TradeOrderDO order, TradeOrderOperateTypeEnum operateType, @Nullable TradeOrderArriveDTO dto) {
        // 更新 TradeOrderDO 状态为已完成
        TradeOrderDO tradeOrderDO = new TradeOrderDO();
        tradeOrderDO.setStatus(TradeOrderStatusEnum.COMPLETED.getStatus());
        tradeOrderDO.setDineStatus(DineStatusEnum.ARRIVED.getValue());
        tradeOrderDO.setReceiveTime(LocalDateTime.now());
        if (dto != null) {
            tradeOrderDO.setRiderPicUrls(dto.getPicUrls());
            tradeOrderDO.setRiderRemark(dto.getRemark());
        }
        int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), tradeOrderDO);
        if (updateCount == 0) {
            throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED);
        }
        // 插入订单日志
        saveTradeOrderLog(order.getId(), operateType, order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus(), null);
        order.setStatus(TradeOrderStatusEnum.COMPLETED.getStatus());
        order.setDineStatus(DineStatusEnum.ARRIVED.getValue());
        order.setReceiveTime(LocalDateTime.now());
        tradeOrderProducer.sendTradeOrderCompletedMessage(TradeOrderConvert.INSTANCE.convert(order));
        for (TradeOrderHandler handler : tradeOrderHandlers) {
            try {
                handler.afterCompleteOrder(order);
            } catch (KnownServiceException ex) {
                log.error("[completeOrder][handler({}) afterCompleteOrder({})] 执行异常：{}", handler.getClass().getSimpleName(), order.getId(), ex.getMessage());
                throw ex; // 继续抛出异常，任意一个回调有异常都进行事务回滚
            } catch (Throwable ex) {
                log.error("[completeOrder][handler({}) afterCompleteOrder({})] 执行异常", handler.getClass().getSimpleName(), order.getId(), ex);
                throw ex; // 继续抛出异常，任意一个回调有异常都进行事务回滚
            }
        }

    }

    /**
     * 校验交易订单满足可收货的条件
     * <p>
     * 1. 交易订单待收货
     *
     * @param userId 用户编号
     * @param id     交易订单编号
     * @return 交易订单
     */
    private TradeOrderDO validateOrderReceivable(Long userId, Long id) {
        // 校验订单是否存在
        TradeOrderDO order = tradeOrderMapper.selectByIdAndUserId(id, userId);
        if (order == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        // 校验订单是否是待收货状态
        if (!TradeOrderStatusEnum.isDoing(order.getStatus())) {
            throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED);
        }
        return order;
    }

    /**
     * 校验交易订单满足可送达的条件
     * <p>
     * 1. 交易订单配送中
     *
     * @param id     交易订单编号
     * @return 交易订单
     */
    private TradeOrderDO validateOrderArrival(Long id) {
        // 校验订单是否存在
        TradeOrderDO order = validateOrderExists(id);
        // 校验订单是否是配送中状态
        Assertions.equals(DineStatusEnum.DELIVERING.getValue(), order.getDineStatus(), "交易订单送达失败，订单不是【配送中】状态");
        return order;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelOrderByBuyer(Long userId, Long id) {
        // 1.1 校验存在
        TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId);
        if (order == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        // 1.2 校验状态
        if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) {
            throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
        }
        // 2. 取消订单
        cancelOrder(order, TradeOrderCancelTypeEnum.BUYER_CANCEL, TradeOrderOperateTypeEnum.MEMBER_CANCEL);
    }

    @Override
    public int cancelOrderBySystem() {
        // 1. 查询过期的待支付订单
        LocalDateTime expireTime = LocalDateTime.now().minusMinutes(PAY_TIMEOUT_MINUTES);
        List<TradeOrderDO> orders = tradeOrderMapper.selectListByStatusAndCreateTimeLt(
                TradeOrderStatusEnum.UNPAID.getStatus(), expireTime);
        if (CollUtil.isEmpty(orders)) {
            return 0;
        }
        // 2. 遍历执行，逐个取消
        int count = 0;
        for (TradeOrderDO order : orders) {
            try {
                getSelf().cancelOrderBySystem(order);
                count++;
            } catch (Throwable e) {
                log.error("[cancelOrderBySystem][order({}) 系统自动取消交易订单异常]", order.getId(), e);
            }
        }
        return count;
    }

    /**
     * 自动取消支付超时的订单
     *
     * @param order 订单
     */
    @Transactional(rollbackFor = Exception.class)
    public void cancelOrderBySystem(TradeOrderDO order) {
        cancelOrder(order, TradeOrderCancelTypeEnum.PAY_TIMEOUT, TradeOrderOperateTypeEnum.SYSTEM_CANCEL);
    }

    /**
     * 取消订单的核心实现
     *
     * @param order      订单
     * @param cancelType 取消类型
     */
    private void cancelOrder(TradeOrderDO order, TradeOrderCancelTypeEnum cancelType, TradeOrderOperateTypeEnum operateType) {
        // 0. 校验是否支持延迟（不允许取消）
        if (TradeOrderStatusEnum.isUnpaid(order.getStatus())) {
            PayOrderVO payOrder = payOrderApi.getOrderByNo(order.getNo());
            if (payOrder != null && UnifiedOrderStatusEnum.isSuccess(payOrder.getStatus())) {
                log.warn("[cancelOrderByBuyer][order({}) 订单已付款（支付回调延迟），不支持取消]", order.getNo());
                throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
            }
        }
        // 1. 更新 TradeOrderDO 状态为已取消
        TradeOrderDO tradeOrderDO = new TradeOrderDO();
        tradeOrderDO.setStatus(TradeOrderStatusEnum.CLOSED.getStatus());
        tradeOrderDO.setCancelType(cancelType.getType());
        tradeOrderDO.setCancelTime(LocalDateTime.now());
        int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), tradeOrderDO);
        if (updateCount == 0) {
            throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
        }
        // 2. 执行 TradeOrderHandler 的后置处理
        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
        for (TradeOrderHandler handler : tradeOrderHandlers) {
            try {
                handler.afterCancelOrder(order, orderItems);
            } catch (KnownServiceException ex) {
                log.error("[cancelOrder][handler({}) afterCancelOrder({})] 执行异常：{}", handler.getClass().getSimpleName(), order.getId(), ex.getMessage());
                throw ex; // 继续抛出异常，任意一个回调有异常都进行事务回滚
            } catch (Throwable ex) {
                log.error("[cancelOrder][handler({}) afterCancelOrder({})] 执行异常", handler.getClass().getSimpleName(), order.getId(), ex);
                throw ex; // 继续抛出异常，任意一个回调有异常都进行事务回滚
            }
        }
        // 3. 增加订单日志
        saveTradeOrderLog(order.getId(), operateType, order.getStatus(), TradeOrderStatusEnum.CLOSED.getStatus(), null);
    }

    /**
     * 如果金额全部被退款，则取消订单
     * 如果还有未被退款的金额，则无需取消订单
     *
     * @param order       订单
     * @param refundPrice 退款金额
     */
    private void cancelOrderIfNecessaryByAfterSale(TradeOrderDO order, Integer refundPrice) {
        // 1. 更新订单
        boolean refundAll = Objects.equals(order.getRefundStatus(), TradeOrderRefundStatusEnum.ALL.getStatus());
        log.info("[cancelOrderIfNecessaryByAfterSale][付款金额({})，退款金额({})，全部退款({})]", order.getPayPrice(), refundPrice, refundAll);
        if (!refundAll) {
            saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.SYSTEM_AFTER_SALE_PART, order.getStatus(), order.getStatus(), null);
            return;
        }
        TradeOrderDO tradeOrderDO = new TradeOrderDO();
        tradeOrderDO.setId(order.getId());
        tradeOrderDO.setStatus(TradeOrderStatusEnum.CLOSED.getStatus());
        tradeOrderDO.setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType());
        tradeOrderDO.setCancelTime(LocalDateTime.now());
        tradeOrderMapper.updateById(tradeOrderDO);
        // 2. 执行 TradeOrderHandler 的后置处理
        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
        for (TradeOrderHandler handler : tradeOrderHandlers) {
            try {
                handler.afterCancelOrder(order, orderItems);
            } catch (KnownServiceException ex) {
                log.error("[cancelOrderIfNecessaryByAfterSale][handler({})] 执行异常：{}", handler.getClass().getName(), ex.getMessage());
                throw ex; // 继续抛出异常，任意一个回调有异常都进行事务回滚
            } catch (Throwable ex) {
                log.error("[cancelOrderIfNecessaryByAfterSale][handler({})] 执行异常", handler.getClass().getName(), ex);
                throw ex; // 继续抛出异常，任意一个回调有异常都进行事务回滚
            }
        }
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.SYSTEM_CANCEL_BY_AFTER_SALE, order.getStatus(), TradeOrderStatusEnum.CLOSED.getStatus(), null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteOrder(Long userId, Long id) {
        // 1.1 校验存在
        TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId);
        if (order == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        // 1.2 校验状态
        if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.CLOSED.getStatus())) {
            throw exception(ORDER_DELETE_FAIL_STATUS_NOT_CANCEL);
        }
        // 2. 删除订单
        tradeOrderMapper.deleteById(id);
        // 3. 记录日志
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.MEMBER_DELETE, order.getStatus(), order.getStatus(), null);
    }

    @Override
    public void updateOrderRemark(TradeOrderRemarkReqVO reqVO) {
        // 校验并获得交易订单
        validateOrderExists(reqVO.getId());
        // 更新
        TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(reqVO);
        tradeOrderMapper.updateById(order);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateOrderPrice(TradeOrderUpdatePriceReqVO reqVO) {
        // 1.1 校验交易订单
        TradeOrderDO order = validateOrderExists(reqVO.getId());
        if (order.getPayStatus()) {
            throw exception(ORDER_UPDATE_PRICE_FAIL_PAID);
        }
//        // TODO 允许多次调价
//        // 1.2 校验调价金额是否变化
//        if (order.getAdjustPrice() != 0) {
//            throw exception(ORDER_UPDATE_PRICE_FAIL_ALREADY);
//        }
        // 1.3 支付价格不能为 0
        int newPayPrice = NumberUtil.nullToZero(order.getPayPrice()) + NumberUtil.nullToZero(reqVO.getAdjustPrice());
        if (newPayPrice <= 0) {
            throw exception(ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR);
        }
        // 2. 更新订单
        TradeOrderDO tradeOrderDO = new TradeOrderDO();
        tradeOrderDO.setId(order.getId());
        tradeOrderDO.setAdjustPrice(reqVO.getAdjustPrice() + order.getAdjustPrice());
        tradeOrderDO.setPayPrice(newPayPrice);
        tradeOrderMapper.updateById(tradeOrderDO);
        // 3. 更新 TradeOrderItem，需要做 adjustPrice 的分摊
        List<TradeOrderItemDO> orderOrderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
        List<Integer> dividePrices = TradePriceCalculatorHelper.dividePrice2(orderOrderItems, reqVO.getAdjustPrice());
        List<TradeOrderItemDO> updateItems = new ArrayList<>();
        for (int i = 0; i < orderOrderItems.size(); i++) {
            TradeOrderItemDO item = orderOrderItems.get(i);
            TradeOrderItemDO tradeOrderItemDO = new TradeOrderItemDO();
            tradeOrderItemDO.setId(item.getId());
            tradeOrderItemDO.setAdjustPrice(item.getAdjustPrice() + dividePrices.get(i));
            tradeOrderItemDO.setPayPrice(item.getPayPrice() + dividePrices.get(i));
            updateItems.add(tradeOrderItemDO);
        }
        tradeOrderItemMapper.updateBatch(updateItems);
        // 4. 更新支付订单
        payOrderApi.updatePayOrderPrice(order.getPayNo(), newPayPrice);
        // 5. 记录订单日志
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.ADMIN_UPDATE_PRICE, order.getStatus(), order.getStatus(), MapUtil.<String, Object>builder().put("oldPayPrice", MoneyUtils.fenToYuanStr(NumberUtil.nullToZero(order.getPayPrice()))).put("adjustPrice", MoneyUtils.fenToYuanStr(NumberUtil.nullToZero(reqVO.getAdjustPrice()))).put("newPayPrice", MoneyUtils.fenToYuanStr(newPayPrice)).build());
    }

    @Override
    public void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO) {
        // 校验交易订单
        TradeOrderDO order = validateOrderExists(reqVO.getId());
        // 只有待发货状态，才可以修改订单收货地址；
        if (!TradeOrderStatusEnum.isPaid(order.getStatus())) {
            throw exception(ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED);
        }
        // 更新
        tradeOrderMapper.updateById(TradeOrderConvert.INSTANCE.convert(reqVO));
        // 记录订单日志
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.ADMIN_UPDATE_ADDRESS, order.getStatus(), order.getStatus(), null);
    }

    @Override
    public void verifyOrderByAdmin(Long adminUserId, Long id) {
        TradeOrderDO orderDO = tradeOrderMapper.selectById(id);
        ShopStaffVO verifyStaffInfo = validateOrderVerifiable(adminUserId, UserTypeEnum.ADMIN.getValue(), orderDO);
        completeOrder(orderDO, TradeOrderOperateTypeEnum.ADMIN_VERIFY, null);
        saveVerifyRecord(verifyStaffInfo.getContactName(), verifyStaffInfo.getContactMobile(), orderDO);
    }

    @Override
    public void verifyOrderByAdmin(Long adminUserId, String verifyCode) {
        TradeOrderDO orderDO = tradeOrderMapper.selectOneByVerifyCode(verifyCode);
        ShopStaffVO verifyStaffInfo = validateOrderVerifiable(adminUserId, UserTypeEnum.ADMIN.getValue(), orderDO);
        completeOrder(orderDO, TradeOrderOperateTypeEnum.ADMIN_VERIFY, null);
        saveVerifyRecord(verifyStaffInfo.getContactName(), verifyStaffInfo.getContactMobile(), orderDO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void verifyOrderByMember(Long memberUserId, String verifyCode) {
        TradeOrderDO orderDO = tradeOrderMapper.selectOneByVerifyCode(verifyCode);
        ShopStaffVO verifyStaffInfo = validateOrderVerifiable(memberUserId, UserTypeEnum.MEMBER.getValue(), orderDO);
        completeOrder(orderDO, TradeOrderOperateTypeEnum.SHOP_STAFF_VERIFY, null);
        saveVerifyRecord(verifyStaffInfo.getContactName(), verifyStaffInfo.getContactMobile(), orderDO);
    }

    private ShopStaffVO validateOrderVerifiable(Long userId, Integer userType, TradeOrderDO orderDO) {
        if (orderDO == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        if (StrUtil.isBlank(orderDO.getVerifyCode())) {
            throw exception(ORDER_COMPLETE_FAIL_VERIFY_CODE_EMPTY);
        }
        if (!TradeOrderStatusEnum.isDoing(orderDO.getStatus())) {
            throw exception(ORDER_VERIFY_FAIL_STATUS_NOT_DOING);
        }
        if (orderDO.getPickUpStoreId() != null && orderDO.getPickUpStoreId() > 0) {
            // 检查门店核销权限
            DeliveryPickUpStoreDO deliveryPickUpStore = pickUpStoreService.getDeliveryPickUpStore(orderDO.getPickUpStoreId());
            if (deliveryPickUpStore == null || !CollUtil.contains(deliveryPickUpStore.getVerifyUserIds(), userId)) {
                throw exception(TRADE_VERIFY_FAIL_NOT_PERMISSION);
            }
        }
        ShopStaffVO verifyStaffInfo = shopStaffApi.getByUserId(orderDO.getShopId(), userId, userType);
        if (verifyStaffInfo == null) {
            throw exception(TRADE_VERIFY_FAIL_NOT_PERMISSION);
        }
        // 如果是拼团订单，则校验拼团是否成功
        if (TradeOrderTypeEnum.isCombination(orderDO.getType())) {
            CombinationRecordRespDTO combinationRecord = combinationRecordApi.getCombinationRecordByOrderId(
                    orderDO.getUserId(), orderDO.getId());
            if (!CombinationRecordStatusEnum.isSuccess(combinationRecord.getStatus())) {
                throw exception(ORDER_VERIFY_FAIL_COMBINATION_NOT_SUCCESS);
            }
        }
        return verifyStaffInfo;
    }

    private void saveVerifyRecord(String verifierName, String verifierMobile, TradeOrderDO orderDO) {
        TradeVerifyRecordSaveDTO reqVO = new TradeVerifyRecordSaveDTO();
        reqVO.setUserId(orderDO.getUserId());
        reqVO.setShopId(orderDO.getShopId());
        reqVO.setCode(orderDO.getVerifyCode());
        reqVO.setBizType(VerifyBizType.TRADE_ORDER.getValue());
        reqVO.setBizId(orderDO.getNo());
        reqVO.setVerifierName(verifierName);
        reqVO.setVerifierMobile(verifierMobile);
        reqVO.setNote(TradeOrderTypeEnum.getByType(orderDO.getType()).getName());
        tradeVerifyRecordService.createVerifyRecord(reqVO);
    }

    @Override
    public TradeOrderDO getByVerifyCode(Long userId, Integer userType, String verifyCode) {
        TradeOrderDO orderDO = tradeOrderMapper.selectOneByVerifyCode(verifyCode);
        if (orderDO == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        ShopStaffVO verifyStaffInfo = shopStaffApi.getByUserId(orderDO.getShopId(), userId, userType);
        if (verifyStaffInfo == null) {
            // 没有该店铺订单的核销权限，则不返回订单信息
            throw exception(TRADE_VERIFY_FAIL_NOT_PERMISSION);
        }
        return orderDO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateOrderItemWhenAfterSaleCreate(Long id, Long orderId, Long afterSaleId) {
        // 更新订单项
        updateOrderItemAfterSaleStatus(id, orderId, AfterSaleStatusEnum.NONE.getStatus(),
                AfterSaleStatusEnum.APPLY.getStatus(), afterSaleId);
        if (isAllOrderItemAfterSaleApply(orderId)) {
            TradeOrderDO order = tradeOrderMapper.selectById(orderId);
            saveTradeOrderLog(orderId, TradeOrderOperateTypeEnum.MEMBER_AFTER_SALE_APPLY, order.getStatus(), order.getStatus(), null);
        }
    }

    private boolean isAllOrderItemAfterSaleApply(Long orderId) {
        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
        if (CollUtil.isEmpty(orderItems)) {
            return true;
        }
        return orderItems.stream().allMatch(orderItem -> Objects.equals(orderItem.getAfterSaleStatus(), AfterSaleStatusEnum.APPLY.getStatus()));
    }

    @Override
    public void updateOrderItemWhenAfterSaleDoing(Long id, Long orderId, Integer oldStatus, Integer newStatus) {
        updateOrderItemAfterSaleStatus(id, orderId, oldStatus, newStatus, null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateOrderItemWhenAfterSaleSuccess(Long id, Long orderId, Integer refundPrice) {
        // 1.1 更新订单项
        updateOrderItemAfterSaleStatus(id, orderId, AfterSaleStatusEnum.APPLY.getStatus(),
                AfterSaleStatusEnum.COMPLETE.getStatus(), null);
        // 1.2 执行 TradeOrderHandler 的后置处理
        TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id);
        TradeOrderItemRespDTO orderItemRespDTO = BeanUtils.toBean(orderItem, TradeOrderItemRespDTO.class);
        TradeOrderDO order = tradeOrderMapper.selectById(orderId);
        TradeOrderRespDTO orderRespDTO = BeanUtils.toBean(order, TradeOrderRespDTO.class);
        for (TradeOrderHandler handler : tradeOrderHandlers) {
            try {
                // 订单售后成功后，回调订单创建后流程，完成诸如积分扣减
                handler.afterOrderCreate(order, Collections.singletonList(orderItem));
            } catch (Throwable ex) {
                log.error("[updateOrderItemWhenAfterSaleSuccess][handler({})] 处理异常",
                        handler.getClass().getName(), ex);
            }
        }
        // 2.1 更新订单的退款金额、积分、联盟豆
        Integer orderRefundPrice = NumberUtil.nullToZero(order.getRefundPrice()) + refundPrice;
        Integer orderRefundPoint = NumberUtil.nullToZero(order.getRefundPoint()) + orderItem.getDeductPoint();
        Double orderRefundCryptocurrency = NumberUtil.nullToZero(order.getRefundCryptocurrency()) + orderItem.getDeductCryptocurrency();
        Integer refundStatus = isAllOrderItemAfterSaleSuccess(order.getId()) ?
                TradeOrderRefundStatusEnum.ALL.getStatus() // 如果都售后成功，则需要取消订单
                : TradeOrderRefundStatusEnum.PART.getStatus();
        TradeOrderDO tradeOrderDO = new TradeOrderDO();
        tradeOrderDO.setId(order.getId());
        tradeOrderDO.setRefundStatus(refundStatus);
        tradeOrderDO.setRefundPrice(orderRefundPrice);
        tradeOrderDO.setRefundPoint(orderRefundPoint);
        tradeOrderDO.setRefundCryptocurrency(orderRefundCryptocurrency);
        tradeOrderMapper.updateById(tradeOrderDO);
        // 2.2 如果全部退款，则进行取消订单
        cancelOrderIfNecessaryByAfterSale(order, orderRefundPrice);
        // 事务提交后再发送售后完成消息
        TransactionUtils.afterCommit(() -> {
            tradeOrderProducer.sendTradeAfterSaleCompletedMessage(orderRespDTO, orderItemRespDTO);
            return null;
        });
    }

    @Override
    public void updateOrderItemWhenAfterSaleCancel(Long id, Long orderId) {
        // 更新订单项
        updateOrderItemAfterSaleStatus(id, orderId, AfterSaleStatusEnum.APPLY.getStatus(),
                AfterSaleStatusEnum.NONE.getStatus(), null);
    }

    private void updateOrderItemAfterSaleStatus(Long id, Long orderId, Integer oldAfterSaleStatus, Integer newAfterSaleStatus, Long afterSaleId) {
        // 更新订单项
        int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, afterSaleId);
        if (updateCount <= 0) {
            log.warn("[updateOrderItemAfterSaleStatus][id({}) oldAfterSaleStatus({}) newAfterSaleStatus({}) afterSaleId({})] 更新失败", id, oldAfterSaleStatus, newAfterSaleStatus, afterSaleId);
            throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL);
        }
        if (Objects.equals(newAfterSaleStatus, AfterSaleStatusEnum.APPLY.getStatus())) {
            TradeOrderDO order = new TradeOrderDO();
            order.setId(orderId);
            order.setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus());
            tradeOrderMapper.updateById(order);
        } else if (Objects.equals(newAfterSaleStatus, AfterSaleStatusEnum.NONE.getStatus())) {
            TradeOrderDO order = new TradeOrderDO();
            order.setId(orderId);
            order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
            tradeOrderMapper.updateById(order);
        }
    }

    private boolean isAllOrderItemAfterSaleSuccess(Long orderId) {
        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
        if (CollUtil.isEmpty(orderItems)) {
            return true;
        }
        return orderItems.stream().allMatch(orderItem -> Objects.equals(orderItem.getAfterSaleStatus(), AfterSaleStatusEnum.COMPLETE.getStatus()));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createOrderCommentByMember(Long userId, AppTradeOrderEvaluationCreateReqVO createReqVO) {
        // 1 校验订单相关状态
        TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(createReqVO.getOrderId(), userId);
        if (order == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus())) {
            throw exception(ORDER_COMMENT_FAIL_STATUS_NOT_COMPLETED);
        }
        if (ObjectUtil.notEqual(order.getCommentStatus(), Boolean.FALSE)) {
            throw exception(ORDER_COMMENT_STATUS_NOT_FALSE);
        }
        // 2. 创建评价
        Long commentId = createOrderComment0(userId, order, createReqVO);
        // 3. 更新订单评价状态
        TradeOrderDO tradeOrderDO = new TradeOrderDO();
        tradeOrderDO.setId(order.getId());
        tradeOrderDO.setCommentStatus(Boolean.TRUE);
        tradeOrderDO.setFinishTime(LocalDateTime.now());
        tradeOrderMapper.updateById(tradeOrderDO);
        // 增加订单日志。注意：只有在所有订单项都评价后，才会增加
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.MEMBER_COMMENT, order.getStatus(), order.getStatus(), null);
        return commentId;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createOrderItemCommentByMember(Long userId, AppTradeOrderItemEvaluationCreateReqVO createReqVO) {
        // 1.1 先通过订单项 ID，查询订单项是否存在
        TradeOrderItemDO orderItem = tradeOrderItemMapper.selectByIdAndUserId(createReqVO.getOrderItemId(), userId);
        if (orderItem == null) {
            throw exception(ORDER_ITEM_NOT_FOUND);
        }
        // 1.2 校验订单相关状态
        TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderItem.getOrderId(), userId);
        if (order == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus())) {
            throw exception(ORDER_COMMENT_FAIL_STATUS_NOT_COMPLETED);
        }
        if (ObjectUtil.notEqual(order.getCommentStatus(), Boolean.FALSE)) {
            throw exception(ORDER_COMMENT_STATUS_NOT_FALSE);
        }
        // 2. 创建评价
        Long commentId = createOrderItemComment0(userId, orderItem, createReqVO);
        // 3. 如果订单项都评论了，则更新订单评价状态
        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
        if (CollUtil.isEmpty(orderItems) || !anyMatch(orderItems, item -> Objects.equals(item.getCommentStatus(), Boolean.FALSE))) {
            TradeOrderDO tradeOrderDO = new TradeOrderDO();
            tradeOrderDO.setId(order.getId());
            tradeOrderDO.setCommentStatus(Boolean.TRUE);
            tradeOrderDO.setFinishTime(LocalDateTime.now());
            tradeOrderMapper.updateById(tradeOrderDO);
            // 增加订单日志。注意：只有在所有订单项都评价后，才会增加
            saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.MEMBER_COMMENT, order.getStatus(), order.getStatus(), null);
        }
        return commentId;
    }

    @Override
    public int createOrderItemCommentBySystem() {
        // 1. 查询过期的待支付订单
        LocalDateTime expireTime = LocalDateTime.now().minus(tradeOrderProperties.getCommentExpireTime());
        List<TradeOrderDO> orders = tradeOrderMapper.selectListByStatusAndReceiveTimeLt(
                TradeOrderStatusEnum.COMPLETED.getStatus(), expireTime, false);
        if (CollUtil.isEmpty(orders)) {
            return 0;
        }

        // 2. 遍历执行，逐个取消
        int count = 0;
        for (TradeOrderDO order : orders) {
            try {
                getSelf().createOrderItemCommentBySystem(order);
                count++;
            } catch (Throwable e) {
                log.error("[createOrderItemCommentBySystem][order({}) 系统自动评价交易订单异常]", order.getId(), e);
            }
        }
        return count;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId) {
        TradeOrderDO tradeOrderDO = new TradeOrderDO();
        tradeOrderDO.setId(orderId);
        tradeOrderDO.setCombinationActivityId(activityId);
        tradeOrderDO.setCombinationRecordId(combinationRecordId);
        tradeOrderDO.setCombinationHeadId(headId);
        tradeOrderMapper.updateById(tradeOrderDO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelPaidOrderByBuyer(Long userId, Long orderId, Integer cancelType) {
        // 1.1 这里校验下 cancelType 只允许拼团关闭；
        if (ObjUtil.notEqual(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getType(), cancelType)) {
            return;
        }
        // 1.2 检验订单存在
        TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId);
        if (order == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        // 1.3 校验订单是否支付
        if (!order.getPayStatus()) {
            throw exception(ORDER_CANCEL_PAID_FAIL, "已支付");
        }
        // 1.4 校验订单是否未退款
        if (ObjUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
            throw exception(ORDER_CANCEL_PAID_FAIL, "未退款");
        }
        // 2.1 取消订单
        cancelOrder(order, TradeOrderCancelTypeEnum.COMBINATION_CLOSE, TradeOrderOperateTypeEnum.MEMBER_CANCEL);
        // 2.2 创建退款单
        PayRefundCreateDTO createDTO = new PayRefundCreateDTO();
        createDTO.setTradeNo(order.getNo());
        createDTO.setPayNo(order.getPayNo());
        createDTO.setUserIp(NetUtil.getLocalhostStr()); // 使用本机 IP，因为是服务器发起退款的
        createDTO.setUserUa(getUserAgent());
        createDTO.setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName());
        createDTO.setRefundPrice(order.getPayPrice());
        payRefundApi.createRefund(createDTO); // 价格信息
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelPaidOrderBySeller(Long orderId) {
        TradeOrderDO order = tradeOrderMapper.selectOne(TradeOrderDO::getId, orderId);
        if (order == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        cancelOrder(order, TradeOrderCancelTypeEnum.SELLER_CANCEL, TradeOrderOperateTypeEnum.ADMIN_CANCEL);
    }

    @Override
    public void updateOrderGiveCouponIds(Long userId, Long orderId, List<Long> giveCouponIds) {
        // 1. 检验订单存在
        TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId);
        if (order == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        // 2. 更新订单赠送的优惠券编号列表
        TradeOrderDO tradeOrderDO = new TradeOrderDO();
        tradeOrderDO.setId(orderId);
        tradeOrderDO.setGiveCouponIds(giveCouponIds);
        tradeOrderMapper.updateById(tradeOrderDO);
    }

    /**
     * 创建单个订单的评论
     *
     * @param order 订单
     */
    @Transactional(rollbackFor = Exception.class)
    public void createOrderItemCommentBySystem(TradeOrderDO order) {
        // 1. 查询未评论的订单项
        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderIdAndCommentStatus(
                order.getId(), Boolean.FALSE);
        if (CollUtil.isEmpty(orderItems)) {
            return;
        }
        // 2. 逐个评论
        for (TradeOrderItemDO orderItem : orderItems) {
            // 2.1 创建评价
            createOrderItemComment0(orderItem.getUserId(), orderItem, buildOrderItemCommentCreateReqVO(orderItem));
            // 2.2 更新订单项评价状态
            TradeOrderItemDO tradeOrderItemDO = new TradeOrderItemDO();
            tradeOrderItemDO.setId(orderItem.getId());
            tradeOrderItemDO.setCommentStatus(Boolean.TRUE);
            tradeOrderItemMapper.updateById(tradeOrderItemDO);
        }
        // 3. 所有订单项都评论了，则更新订单评价状态
        TradeOrderDO tradeOrderDO = new TradeOrderDO();
        tradeOrderDO.setId(order.getId());
        tradeOrderDO.setCommentStatus(Boolean.TRUE);
        tradeOrderDO.setFinishTime(LocalDateTime.now());
        tradeOrderMapper.updateById(tradeOrderDO);
        // 增加订单日志。注意：只有在所有订单项都评价后，才会增加
        saveTradeOrderLog(order.getId(), TradeOrderOperateTypeEnum.SYSTEM_COMMENT, order.getStatus(), order.getStatus(), null);
    }

    @Nonnull
    private AppTradeOrderItemEvaluationCreateReqVO buildOrderItemCommentCreateReqVO(TradeOrderItemDO orderItem) {
        AppTradeOrderItemEvaluationCreateReqVO commentCreateReqVO = new AppTradeOrderItemEvaluationCreateReqVO();
        commentCreateReqVO.setOrderItemId(orderItem.getId());
        commentCreateReqVO.setAnonymous(false);
        commentCreateReqVO.setContent(""); // 超时未评价，系统默认好评
        commentCreateReqVO.setScores(ScoreLevelEnum.STAR_5.getValue());
        commentCreateReqVO.setBenefitScores(ScoreLevelEnum.STAR_5.getValue());
        commentCreateReqVO.setDescriptionScores(ScoreLevelEnum.STAR_5.getValue());
        return commentCreateReqVO;
    }

    /**
     * 创建订单的评论的核心实现
     *
     * @param userId   用户编号
     * @param orderDO   订单项
     * @param createReqVO 评论内容
     * @return 评论编号
     */
    private Long createOrderComment0(Long userId, TradeOrderDO orderDO, AppTradeOrderEvaluationCreateReqVO createReqVO) {
        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderDO.getId());
        if (CollUtil.isEmpty(orderItems)) {
            throw exception(ORDER_ITEM_NOT_FOUND);
        }
        Long lastCommentId = 0L;
        for (TradeOrderItemDO orderItem : orderItems) {
            AppTradeOrderItemEvaluationCreateReqVO reqVO = BeanUtils.toBean(createReqVO, AppTradeOrderItemEvaluationCreateReqVO.class);
            reqVO.setOrderItemId(orderItem.getId());
            lastCommentId = createOrderItemComment0(userId, orderItem, reqVO);
        }
        return lastCommentId;
    }

    /**
     * 创建订单项的评论的核心实现
     *
     * @param userId   用户编号
     * @param orderItem   订单项
     * @param createReqVO 评论内容
     * @return 评论编号
     */
    private Long createOrderItemComment0(Long userId, TradeOrderItemDO orderItem, AppTradeOrderItemEvaluationCreateReqVO createReqVO) {
        // 1. 创建评价
        ProductEvaluationCreateReqDTO dto = TradeOrderConvert.INSTANCE.convert04(createReqVO, orderItem);
        Long commentId = productEvaluationApi.createComment(userId, dto);

        // 2. 更新订单项评价状态
        TradeOrderItemDO tradeOrderItemDO = new TradeOrderItemDO();
        tradeOrderItemDO.setId(orderItem.getId());
        tradeOrderItemDO.setCommentStatus(Boolean.TRUE);
        tradeOrderItemMapper.updateById(tradeOrderItemDO);
        return commentId;
    }

    private void saveTradeOrderLog(Long tradeOrderId, TradeOrderOperateTypeEnum operateType, Integer beforeStatus, Integer afterStatus, @Nullable Map<String, Object> extra) {
        log.debug("[saveTradeOrderLog({})] operateType({}) beforeStatus({}) afterStatus({}) extra({})", tradeOrderId, operateType, beforeStatus, afterStatus, extra);
        // 1.0 订单信息
        if (tradeOrderId == null || tradeOrderId <= 0) { // 如果未设置，只有注解，说明不需要记录日志
            return;
        }
        try {
            // 1.1 操作用户
            Integer userType = ObjectUtil.defaultIfNull(WebFrameworkUtils.getLoginUserType(), TradeOrderLogDO.USER_TYPE_SYSTEM);
            Long userId = ObjectUtil.defaultIfNull(WebFrameworkUtils.getLoginUserId(), TradeOrderLogDO.USER_ID_SYSTEM);
            Map<String, Object> extraMap = ObjectUtil.defaultIfNull(extra, Map.of());
            String content = StrUtil.format(operateType.getContent(), extraMap);
            // 2. 记录日志
            TradeOrderLogCreateBO createBO = new TradeOrderLogCreateBO();
            createBO.setUserId(userId);
            createBO.setUserType(userType);
            createBO.setOrderId(tradeOrderId);
            createBO.setBeforeStatus(beforeStatus);
            createBO.setAfterStatus(afterStatus);
            createBO.setOperateType(operateType.getType());
            createBO.setContent(content);
            tradeOrderLogService.createOrderLog(createBO);
        } catch (Exception e) {
            log.error("[saveTradeOrderLog({}) 订单日志错误]", tradeOrderId, e);
        }
    }

    /**
     * 获得自身的代理对象，解决自调用的 AOP 生效问题、数据库事务问题
     *
     * @return 自己
     */
    private TradeOrderUpdateServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

}
